﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using BitcoinMarket.DataSources;
using Newtonsoft.Json.Linq;
using System.Collections.ObjectModel;
using System.Threading;
using System.Windows.Threading;

namespace BitcoinMarket.DataConnectors
{
    public class BitcoinChartsMarketsDataConnector
    {
        /// <summary>
        /// Callback action when the data is ready
        /// </summary>
        private Action<Collection<BitcoinChartsMarketsCurrencyCollection>> MarketsReadyCallback = null;
        private Action<Collection<TradesData>> TradesReadyCallback = null;

        private JArray MarketsData = null;
        private JObject PricesData = null;
        private Collection<TradesData> TradesData = null;

        /// <summary>
        /// Obtains the markets data from the Bitcoincharts data source
        /// </summary>
        /// <param name="readyCallback">Callback to pass the obtained data</param>
        public void GetMarketsData(Action<Collection<BitcoinChartsMarketsCurrencyCollection>> readyCallback)
        {
            this.GetMarketsData(readyCallback, false);
        }

        /// <summary>
        /// Obtains the markets data from the Bitcoincharts data source
        /// </summary>
        /// <param name="readyCallback">Callback to pass the obtained data</param>
        /// <param name="readyCallback">Load the date from the chace file if true</param>
        public void GetMarketsData(Action<Collection<BitcoinChartsMarketsCurrencyCollection>> readyCallback, bool UseCache)
        {
            //Set the callback
            this.MarketsReadyCallback = readyCallback;

            //Load data from BitcoinCharts
            BitcoinChartsMarketsDataSource BitcoinChartsDataSource = new BitcoinChartsMarketsDataSource();
            BitcoinChartsDataSource.GetMarketsData(this.bitcoinChartsMarketsDataDownloaded, UseCache);
        }

        /// <summary>
        /// Obtains the markets data from the Bitcoincharts data source
        /// </summary>
        /// <param name="ReadyCallback">Callback to pass the obtained data</param>
        public void GetTradesData(Collection<string> MarketNames, Action<Collection<TradesData>> ReadyCallback)
        {
            //Set the callback
            this.TradesReadyCallback = ReadyCallback;

            //Load data from BitcoinCharts
            BitcoinChartsMarketsDataSource BitcoinChartsDataSource = new BitcoinChartsMarketsDataSource();
            BitcoinChartsDataSource.GetTradesData(MarketNames, this.bitcoinChartsTradesDataDownloaded);
        }

        /// <summary>
        /// Callback for Markets data download
        /// Saves the downloaded data to private variables and starts the parser
        /// </summary>
        /// <param name="MarketsData"></param>
        /// <param name="PricesData"></param>
        public void bitcoinChartsMarketsDataDownloaded(JArray MarketsData, JObject PricesData)
        {
            this.MarketsData = MarketsData;
            this.PricesData = PricesData;

            ThreadPool.QueueUserWorkItem(new WaitCallback(bitcoinChartsParseMarketsData));
        }

        /// <summary>
        /// Callback for Trades data download
        /// Saves the downloaded data to private variables and starts the parser
        /// </summary>
        /// <param name="TradesData">Data to parse</param>
        public void bitcoinChartsTradesDataDownloaded(Collection<TradesData> TradesData)
        {
            this.TradesData = TradesData;
            
            //Pass the downloaded data to the callback
            this.TradesReadyCallback(this.TradesData);
            //MessageBox.Show("Ready: " + this.TradesData.Count.ToString());

            //TODO - enable this when aditional parsing will be required
            //ThreadPool.QueueUserWorkItem(new WaitCallback(bitcoinChartsParseTradesData));
        }


        /// <summary>
        /// Parses the downloaded data for BitcoinCharts
        /// </summary>
        /// <param name="o"></param>
        private void bitcoinChartsParseMarketsData(object o)
        {
            //Prepare the Markets data
            Collection<BitcoinChartsMarketsCurrencyCollection> CurrencyDataByCurrencyName = this.groupMarketsData(this.MarketsData);
            this.addPricesData(CurrencyDataByCurrencyName, this.PricesData);

            Deployment.Current.Dispatcher.BeginInvoke(() => {
                this.MarketsReadyCallback(CurrencyDataByCurrencyName);
            });
        }

        /// <summary>
        /// Parses the downloaded data for BitcoinCharts
        /// </summary>
        /// <param name="o"></param>
        private void bitcoinChartsParseTradesData(object o)
        {
            //Nothing to do for now
            //TODO - parse RAW CSV data to OHLC (open, high, low, close)
            //...

            Deployment.Current.Dispatcher.BeginInvoke(() =>
            {
                //For now just return the same data
                this.TradesReadyCallback(this.TradesData);
            });
        }

        /// <summary>
        /// Organizes the markets data into one object by the market's currency
        /// </summary>
        /// <param name="data">Data to organize</param>
        /// <returns>Organized data</returns>
        private Collection<BitcoinChartsMarketsCurrencyCollection> groupMarketsData(JArray data)
        {
            //Temporary variables
            Double lHigh, lBid, lVolume, lCurrencyVolume, lAsk, lOpen, lClose, lAvg, lLow;
            Int64 lLatestTrade;
            string lCurrency, lSymbol;

            //Data to parse into
            //Collection<BitcoinChartsMarketsDataItem> DataItems = new Collection<BitcoinChartsMarketsDataItem>();

            //Data to return
            Collection<BitcoinChartsMarketsCurrencyCollection> CurrencyDataByCurrencyName = new Collection<BitcoinChartsMarketsCurrencyCollection>();

            foreach (JToken token in MarketsData)
            {
                try
                {
                    lHigh = token.Value<Double>("high");
                }
                catch (InvalidCastException)
                {
                    lHigh = -1.0;
                }

                try
                {
                    lLatestTrade = token.Value<Int64>("latest_trade");
                }
                catch (InvalidCastException)
                {
                    lLatestTrade = -1;
                }

                try
                {
                    lBid = token.Value<Double>("bid");
                }
                catch (InvalidCastException)
                {
                    lBid = -1.0;
                }

                try
                {
                    lVolume = token.Value<Double>("volume");
                }
                catch (InvalidCastException)
                {
                    lVolume = -1.0;
                }

                try
                {
                    lCurrencyVolume = token.Value<Double>("currency_volume");
                }
                catch (InvalidCastException)
                {
                    lCurrencyVolume = -1.0;
                }

                try
                {
                    lAsk = token.Value<Double>("ask");
                }
                catch (InvalidCastException)
                {
                    lAsk = -1.0;
                }

                try
                {
                    lOpen = token.Value<Double>("open");
                }
                catch (InvalidCastException)
                {
                    lOpen = -1.0;
                }

                try
                {
                    lClose = token.Value<Double>("close");
                }
                catch (InvalidCastException)
                {
                    lClose = -1.0;
                }

                try
                {
                    lAvg = token.Value<Double>("avg");
                }
                catch (InvalidCastException)
                {
                    lAvg = -1.0;
                }

                try
                {
                    lLow = token.Value<Double>("low");
                }
                catch (InvalidCastException)
                {
                    lLow = -1.0;
                }

                try
                {
                    lCurrency = token.Value<string>("currency");
                }
                catch (InvalidCastException)
                {
                    lCurrency = "-";
                }

                try
                {
                    lSymbol = token.Value<string>("symbol");
                }
                catch (InvalidCastException)
                {
                    lSymbol = "-";
                }

                BitcoinChartsMarketsDataItem DataItem = new BitcoinChartsMarketsDataItem()
                {
                    High = lHigh,
                    LatestTrade = lLatestTrade,
                    Bid = lBid,
                    Volume = lVolume,
                    Currency = lCurrency,
                    CurrencyVolume = lCurrencyVolume,
                    Ask = lAsk,
                    Open = lOpen,
                    Close = lClose,
                    Avg = lAvg,
                    Symbol = lSymbol,
                    Low = lLow
                };

                //DataItems.Add(DataItem);

                //Add the prepared data to the currency collection
                this.addToCurrencyCollection(CurrencyDataByCurrencyName, DataItem);
            }

            return CurrencyDataByCurrencyName;
        }

        /// <summary>
        /// Adds the data about currency prices into the object storing the other currency data
        /// </summary>
        /// <param name="CurrencyData"></param>
        /// <param name="PricesData"></param>
        private void addPricesData(Collection<BitcoinChartsMarketsCurrencyCollection> CurrencyData, JObject PricesData)
        {
            JToken tmpToken;
            Double lHour24, lDay7, lDay30;

            foreach (BitcoinChartsMarketsCurrencyCollection data in CurrencyData)
            {
                tmpToken = PricesData[data.Currency];

                if (tmpToken != null)
                {
                    try
                    {
                        lHour24 = tmpToken.Value<Double>("24h");
                    }
                    catch (InvalidCastException)
                    {
                        lHour24 = -1.0;
                    }

                    try
                    {
                        lDay7 = tmpToken.Value<Double>("7d");
                    }
                    catch (InvalidCastException)
                    {
                        lDay7 = -1.0;
                    }

                    try
                    {
                        lDay30 = tmpToken.Value<Double>("30d");
                    }
                    catch (InvalidCastException)
                    {
                        lDay30 = -1.0;
                    }

                    data.h24 = lHour24;
                    data.d7 = lDay7;
                    data.d30 = lDay30;
                }
                else
                {
                    data.h24 = -1.0;
                    data.d7 = -1.0;
                    data.d30 = -1.0;
                }
            }
        }

        /// <summary>
        /// Add a currency item to the currrency collection organized by the currency name
        /// </summary>
        /// <param name="DataCollection"></param>
        /// <param name="Item"></param>
        private void addToCurrencyCollection(Collection<BitcoinChartsMarketsCurrencyCollection> DataCollection, BitcoinChartsMarketsDataItem Item)
        {
            string currencyName = Item.Currency;
            bool found = false;

            foreach (BitcoinChartsMarketsCurrencyCollection item in DataCollection)
            {
                if (item.Currency.Equals(currencyName))
                {
                    item.DataItems.Add(Item);
                    found = true;
                    break;
                }
            }

            if (!found)
            {
                Collection<BitcoinChartsMarketsDataItem> tmpItems = new Collection<BitcoinChartsMarketsDataItem>();
                tmpItems.Add(Item);

                DataCollection.Add(
                    new BitcoinChartsMarketsCurrencyCollection
                    {
                        Currency = currencyName,
                        DataItems = tmpItems
                    }
                );
            }
        }
    }

    /// <summary>
    /// Class for storing BitcoinCharts data item
    /// </summary>
    /// <example>
    /// {"high": 23.745330000000, "latest_trade": 1360613660, "bid": 23.500000000000, "volume": 1303.549317030000, "currency": "AUD",
    /// "currency_volume": 30646.298362235278, "ask": 23.717480000000, "close": 23.729260000000, "avg": 23.50988793585473633593592525,
    /// "symbol": "mtgoxAUD", "low": 23.000000000000}
    /// </example>
    public class BitcoinChartsMarketsDataItem
    {
        public Double High { get; set; }
        public Int64 LatestTrade { get; set; }
        public Double Bid { get; set; }
        public Double Volume { get; set; }
        public string Currency { get; set; }
        public Double CurrencyVolume { get; set; }
        public Double Ask { get; set; }
        public Double Open { get; set; }
        public Double Close { get; set; }
        public Double Avg { get; set; }
        public string Symbol { get; set; }
        public Double Low { get; set; }        
    }

    /// <summary>
    /// Collection of currency data organized by currency name
    /// </summary>
    public class BitcoinChartsMarketsCurrencyCollection
    {
        /// <summary>
        /// Currency name
        /// </summary>
        public string Currency {get; set;}

        /// <summary>
        /// Average value in the past 24 hours
        /// </summary>
        public double h24 { get; set; }

        /// <summary>
        /// Average value in the past 7 days
        /// </summary>
        public double d7 { get; set; }

        /// <summary>
        /// Average value in the past 30 days
        /// </summary>
        public double d30 { get; set; }

        /// <summary>
        /// Data for the markets using this currency
        /// </summary>
        public Collection<BitcoinChartsMarketsDataItem> DataItems {get; set;}

        /// <summary>
        /// Returns a string containing all symbols
        /// </summary>
        /// <returns>Symbols</returns>
        public string GetSymbolsStr()
        {
            string str = "";

            foreach (BitcoinChartsMarketsDataItem item in this.DataItems)
            {
                str += item.Symbol + ", ";
            }

            return str.Substring(0, str.Length - 2);
        }
    }
}
